Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/last seen at by environment #4939

Merged
merged 16 commits into from
Oct 9, 2023
Merged

Conversation

FredrikOseberg
Copy link
Contributor

@FredrikOseberg FredrikOseberg commented Oct 5, 2023

This is a draft of the new architecture for last seen at. Apologies for size, but I initially explored triggers and couldn't merge that before it was done. I changed my approach mid-way and now I'd like to verify the direction and merge before doing the remaining endpoints.

Discussion

Today there are 5 pages accessing this information:

  • Project health
  • Project overview (DONE)
  • Feature overview
  • Feature toggle list
  • Archived features

The last seen at property is in it's current form being added to the data structure of the environments property that exists on the feature data structure on some of these views. Since some of the views don't have it, its implementation varies across views. I have chosen not to change the data structure in this iteration, which has influenced the design decisions of reading the data.

It might it would be better if the data was added as an additional field directly on the feature data structure itself, only for the read operations in the admin panel:

{ name: 'my-feature ..., lastSeenAtPerEnvironment: [ { 'development': { 'lastSeenAt': '2023-10-03 13:08:16.263,'  } } ] }

Other discussion points:

  • Went with batch inserts instead of triggers. Reasons:
    • Can be feature flagged
    • Triggers run on every row, and application will exponentially increase the amount of rows we need to insert LastSeenAt because it's a dimension we don't care about in the new table
    • Would have to shut down releasing to production while testing trigger performance
  • Foreign key on environments to maintain data integrity when deleting environment

Follow up actions

  • Implement remaining endpoints
  • Increase test coverage
  • Land on data structure
  • Test with load
  • Add job to clean up (IE: Feature flags that do not exist)

@vercel
Copy link

vercel bot commented Oct 5, 2023

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
unleash-docs ❌ Failed (Inspect) Oct 9, 2023 7:26am
unleash-monorepo-frontend ✅ Ready (Inspect) Visit Preview 💬 Add feedback Oct 9, 2023 7:26am

import { LastSeenService } from './last-seen-service';
import LastSeenStore from './last-seen-store';

export const createLastSeenService = (
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use composite root pattern for LastSeenService

import { IFeatureLastSeenResults } from './last-seen-read-model';

export class LastSeenMapper {
mapToFeatures(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Map results of reading into environments of incoming features to maintain todays data structure, might change in follow up iteration

};
};
}
export class LastSeenAtReadModel implements ILastSeenReadModel {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

retrieve last seen at results per environment for an array of features

Comment on lines +55 to +59
if (this.config.flagResolver.isEnabled('useLastSeenRefactor')) {
await this.lastSeenStore.setLastSeen(lastSeenToggles);
} else {
await this.featureToggleStore.setLastSeen(lastSeenToggles);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maintain previous implementation

Comment on lines 44 to 71
async setLastSeen(data: LastSeenInput[]): Promise<void> {
const now = new Date();

try {
const inserts = data.map((item) => {
return {
feature_name: item.featureName,
environment: item.environment,
last_seen_at: now,
};
});

const batchSize = 1000;

await this.db.transaction(async (trx) => {
for (let i = 0; i < inserts.length; i += batchSize) {
const batch = inserts.slice(i, i + batchSize);
// Knex optimizes multi row insert when given an array:
// https://knexjs.org/guide/query-builder.html#insert
await trx(TABLE)
.insert(batch)
.onConflict(['feature_name', 'environment'])
.merge();
}
});
} catch (err) {
this.logger.error('Could not update lastSeen, error: ', err);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Batch insert last seen in batches of 1000

Comment on lines 1080 to 1094
if (this.flagResolver.isEnabled('useLastSeenRefactor')) {
const mapper = new LastSeenMapper();

const featureNames = features.map((feature) => feature.name);
const lastSeenAtPerEnvironment =
await this.lastSeenReadModel.getForFeature(featureNames);

console.log(decoratedFeatures, lastSeenAtPerEnvironment);

decoratedFeatures = mapper.mapToFeatures(
decoratedFeatures,
lastSeenAtPerEnvironment,
this.logger,
);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Decorate features with lastSeenData

exports.up = function (db, callback) {
db.runSql(
`
CREATE TABLE last_seen_at_metrics (
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new table, foreign key on environments

@andreas-unleash
Copy link
Contributor

Looks good. Only suggestion is we could add the batch size as a variant in the flag. Design decisions make sense to me


const batchSize = 1000;

await this.db.transaction(async (trx) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd do the transaction around the service (in the controller) as we do for all new code.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since it's a non critical data I'm wondering if having transactions even makes sense here. I'd consider sacrificing correctness for performance.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, done

this.featureToggleStore = featureToggleStore;
this.logger = config.getLogger(
'/services/client-metrics/last-seen-service.ts',
);
this.config = config;

this.timers.push(
setInterval(() => this.store(), lastSeenInterval).unref(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this be part of the scheduler?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will look into separating this in a follow-up

@FredrikOseberg FredrikOseberg force-pushed the fix/last-seen-at-by-environment branch from b5c94c9 to 9a961a7 Compare October 6, 2023 08:28
};

exports.down = function (db, callback) {
callback();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing down migration

Copy link
Contributor

@kwasniew kwasniew left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd run clinic.js with both last seen variants to compare the performance characteristics

@FredrikOseberg FredrikOseberg merged commit d896dbd into main Oct 9, 2023
14 checks passed
@FredrikOseberg FredrikOseberg deleted the fix/last-seen-at-by-environment branch October 9, 2023 08:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants